package com.gitee.ed.example.retryer;

import cn.hutool.core.util.ObjectUtil;
import com.github.rholder.retry.*;

import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * 重试器构建者
 * 提供更多的重试策略
 *
 * @author hongda.li
 */
public class RetryerBuilder<Result> {

    private final com.github.rholder.retry.RetryerBuilder<Result> builder;

    private RetryerBuilder() {
        this.builder = com.github.rholder.retry.RetryerBuilder.newBuilder();
    }

    /**
     * 构建一个重试构建器
     *
     * @param <Result> 重试结果泛型
     * @return 重试构建器
     */
    public static <Result> RetryerBuilder<Result> newBuilder() {
        return new RetryerBuilder<>();
    }

    /**
     * 自定义配置
     *
     * @param consumer 自定义配置逻辑
     * @return 链式调用
     */
    public RetryerBuilder<Result> configure(Consumer<com.github.rholder.retry.RetryerBuilder<Result>> consumer) {
        consumer.accept(this.builder);
        return this;
    }

    /**
     * 重试指定次数后停止
     *
     * @param times 指定的重试次数
     * @return 链式调用
     */
    public RetryerBuilder<Result> stopAfterAttempt(int times) {
        this.builder.withStopStrategy(StopStrategies.stopAfterAttempt(Math.max(1, times)));
        return this;
    }

    /**
     * 重试指定秒后停止
     *
     * @param seconds 指定的重试时间
     * @return 链式调用
     */
    public RetryerBuilder<Result> stopAfterDelay(long seconds) {
        return this.stopAfterDelay(seconds, TimeUnit.SECONDS);
    }

    /**
     * 重试指定时间后停止
     *
     * @param time 指定的重试时间
     * @param unit 时间单位
     * @return 链式调用
     */
    public RetryerBuilder<Result> stopAfterDelay(long time, TimeUnit unit) {
        this.builder.withStopStrategy(StopStrategies.stopAfterDelay(time, unit));
        return this;
    }

    /**
     * 当发生任意异常时进行重试
     *
     * @return 链式调用
     */
    public RetryerBuilder<Result> retryIfException() {
        this.builder.retryIfException();
        return this;
    }

    /**
     * 当发生指定异常时进行重试
     *
     * @param type 异常类型
     * @return 链式调用
     */
    public RetryerBuilder<Result> retryIfException(Class<? extends Exception> type) {
        this.builder.retryIfExceptionOfType(type);
        return this;
    }

    /**
     * 当发生指定异常信息时进行重试
     *
     * @param message 异常信息
     * @return 链式调用
     */
    public RetryerBuilder<Result> retryIfException(String message) {
        return this.retryIfException(error -> error != null && Objects.equals(error.getMessage(), message));
    }

    /**
     * 当发生指定异常时进行重试
     *
     * @param predicate 异常断言
     * @return 链式调用
     */
    public RetryerBuilder<Result> retryIfException(Predicate<Exception> predicate) {
        this.builder.retryIfException(error -> {
            if (error instanceof Exception e) {
                return predicate.test(e);
            } else {
                return predicate.test(new RuntimeException(error));
            }
        });
        return this;
    }

    /**
     * 当结果为空时进行重试
     *
     * @return 链式调用
     */
    public RetryerBuilder<Result> retryIfNull() {
        return this.retryIfResult(Objects::isNull);
    }

    /**
     * 当结果为空时进行重试
     * 支持 CharSequence、Map、Iterable、Iterator、Array
     *
     * @return 链式调用
     */
    public RetryerBuilder<Result> retryIfEmpty() {
        return this.retryIfResult(ObjectUtil::isEmpty);
    }

    /**
     * 当结果不为空时进行重试
     *
     * @return 链式调用
     */
    public RetryerBuilder<Result> retryIfNotNull() {
        return this.retryIfResult(Objects::nonNull);
    }

    /**
     * 当结果不为空时进行重试
     * 支持 CharSequence、Map、Iterable、Iterator、Array
     *
     * @return 链式调用
     */
    public RetryerBuilder<Result> retryIfNotEmpty() {
        return this.retryIfResult(ObjectUtil::isNotEmpty);
    }

    /**
     * 当出现指定结果时进行重试
     *
     * @param predicate 结果断言
     * @return 链式调用
     */
    public RetryerBuilder<Result> retryIfResult(Predicate<Result> predicate) {
        this.builder.retryIfResult(predicate::test);
        return this;
    }

    /**
     * 睡眠阻塞策略
     * 当第一次重试结束后，线程将进入睡眠状态，直到满足等待时间
     *
     * @return 链式调用
     */
    @SuppressWarnings("StatementWithEmptyBody")
    public RetryerBuilder<Result> spinBlockStrategy() {
        return this.blockStrategy(sleepTime -> {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start < sleepTime) {
                // 自旋阻塞
            }
        });
    }

    /**
     * 自旋阻塞策略
     * 当第一次重试结束后，线程将进入自旋状态，直到满足等待时间
     *
     * @return 链式调用
     */
    public RetryerBuilder<Result> sleepBlockStrategy() {
        return this.blockStrategy(BlockStrategies.threadSleepStrategy());
    }

    /**
     * 设置阻塞策略
     * 阻塞策略作用于两次重试之间的阻塞逻辑
     * 例如设置为睡眠阻塞策略时，当第一次重试结束后，线程将进入睡眠状态，直到第二次满足睡眠时间
     *
     * @param blockStrategy 阻塞策略
     * @return 链式调用
     */
    public RetryerBuilder<Result> blockStrategy(BlockStrategy blockStrategy) {
        this.builder.withBlockStrategy(blockStrategy);
        return this;
    }

    /**
     * 空等待策略
     * 每一次重试后立即执行下一次
     *
     * @return 链式调用
     */
    public RetryerBuilder<Result> noWaitStrategy() {
        this.builder.withWaitStrategy(WaitStrategies.noWait());
        return this;
    }

    /**
     * 固定时间等待策略
     * 每一次重试后等待固定秒后执行下一次
     *
     * @param seconds 等待的秒数
     * @return 链式调用
     */
    public RetryerBuilder<Result> fixedWaitStrategy(long seconds) {
        return this.fixedWaitStrategy(seconds, TimeUnit.SECONDS);
    }

    /**
     * 固定时间等待策略
     * 每一次重试后等待固定时间后执行下一次
     *
     * @param time 等待的时间
     * @param unit 时间单位
     * @return 链式调用
     */
    public RetryerBuilder<Result> fixedWaitStrategy(long time, TimeUnit unit) {
        this.builder.withWaitStrategy(WaitStrategies.fixedWait(time, unit));
        return this;
    }

    /**
     * 随机时间等待策略
     * 每一次重试后等待随机时间后执行下一次
     * 随机时间小于等于最大时间
     *
     * @param seconds 等待的最大秒数
     * @return 链式调用
     */
    public RetryerBuilder<Result> randomWaitStrategy(long seconds) {
        return this.randomWaitStrategy(seconds, TimeUnit.SECONDS);
    }

    /**
     * 随机时间等待策略
     * 每一次重试后等待随机时间后执行下一次
     * 随机时间小于等于最大时间
     *
     * @param time 等待的最大时间
     * @param unit 时间单位
     * @return 链式调用
     */
    public RetryerBuilder<Result> randomWaitStrategy(long time, TimeUnit unit) {
        this.builder.withWaitStrategy(WaitStrategies.randomWait(time, unit));
        return this;
    }

    /**
     * 设置等待策略
     * 等待策略作用于每一次重试后，根据等待策略决定是否立即执行下一次
     *
     * @param waitStrategy 等待策略
     * @return 链式调用
     */
    public RetryerBuilder<Result> waitStrategy(WaitStrategy waitStrategy) {
        this.builder.withWaitStrategy(waitStrategy);
        return this;
    }

    /**
     * 配置失败重试监听器
     * 重试监听器作用于每一次重试失败后
     *
     * @param listener 失败重试监听器
     * @return 链式调用
     */
    public RetryerBuilder<Result> retryFailureListener(Consumer<RetryResult<Result>> listener) {
        return this.retryListener(res -> {
            if (res.result() == null) {
                listener.accept(res);
            }
        });
    }

    /**
     * 配置成功重试监听器
     * 重试监听器作用于每一次重试成功后
     *
     * @param listener 成功重试监听器
     * @return 链式调用
     */
    public RetryerBuilder<Result> retrySuccessListener(Consumer<RetryResult<Result>> listener) {
        return this.retryListener(res -> {
            if (res.throwable() == null) {
                listener.accept(res);
            }
        });
    }

    /**
     * 配置日志监听器
     * 日志监听器在每一次重试后打印相关日志信息
     *
     * @return 日志监听器
     */
    public RetryerBuilder<Result> loggerRetryListener() {
        return this.retryListener(new LoggerListener<>());
    }

    /**
     * 配置重试监听器
     * 重试监听器作用于每一次重试后，无论当前重试是否出现异常
     *
     * @param listener 重试监听器
     * @return 链式调用
     */
    @SuppressWarnings({"UnstableApiUsage", "unchecked"})
    public RetryerBuilder<Result> retryListener(Consumer<RetryResult<Result>> listener) {
        this.builder.withRetryListener(new RetryListener() {
            @Override
            public <IResult> void onRetry(Attempt<IResult> attempt) {
                Throwable throwable;
                if (attempt.hasException()) {
                    throwable = attempt.getExceptionCause();
                } else {
                    throwable = null;
                }

                Result result;
                if (attempt.hasResult()) {
                    result = (Result) attempt.getResult();
                } else {
                    result = null;
                }

                RetryResult<Result> retryResult = new RetryResult<>(
                        result,
                        attempt.getAttemptNumber(),
                        attempt.getDelaySinceFirstAttempt(),
                        throwable
                );

                listener.accept(retryResult);
            }
        });
        return this;
    }

    /**
     * 构建最终的重试器
     *
     * @return 重试器
     */
    public Retryer<Result> build() {
        return builder.build();
    }
}
